package com.alipan.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipan.constant.Constants;
import com.alipan.service.HttpClient;
import kotlin.Pair;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;


@Slf4j
@Service
public class HttpClientImpl implements HttpClient, InitializingBean {

    private String token;

    @Value("${aliyundrive.auth}")
    private String authUrl;

    @Value("${aliyundrive.refresh-token}")
    private String refreshToken;

    private OkHttpClient okHttpClient;

    public HttpClientImpl() {
        OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(chain -> {
                    Request request = chain.request().newBuilder()
                            .addHeader("authorization", "Bearer " + token)
                            .build();
                    return chain.proceed(request);
                }).authenticator((route, response) -> {
                    if (response.code() == 401 && response.body() != null && response.body().string().contains("AccessToken")) {
                        return response.request().newBuilder().removeHeader("authorization").header("authorization", "").build();
                    }
                    return null;
                })
                .readTimeout(1, TimeUnit.MINUTES)
                .writeTimeout(1, TimeUnit.MINUTES)
                .connectTimeout(1, TimeUnit.MINUTES)
                .build();
        this.okHttpClient = okHttpClient;
    }

    @Override
    public String get(Map<String, String> header, String url) {
        if (!url.startsWith("http")) url = Constants.URL + url;
        try {
            HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
            //params.forEach(urlBuilder::addQueryParameter);
            Request request = new Request.Builder().get().url(urlBuilder.build()).build();
            try (Response response = okHttpClient.newCall(request).execute()) {
                log.debug("get {}, code {}", urlBuilder.build(), response.code());
                if (!response.isSuccessful()) {
                    throw new RuntimeException("请求失败：" + urlBuilder.build());
                }
                return response.body().string();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public String post(Map<String, String> header, String url, Object body) {
        if (!url.startsWith("http")) url = Constants.URL + url;
        String bodyAsJson = JSONObject.toJSONString(body);
        Request request = new Request.Builder()
                .post(RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), bodyAsJson))
                .url(url).build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            log.debug("post {}, body {}, code {}", url, bodyAsJson, response.code());
            if (!response.isSuccessful()) {
                log.error("请求失败，url={}, code={}, body={}", url, response.code(), response.body().string());
                throw new RuntimeException("请求失败：" + url);
            }
            return response.body().string();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void upload(Map<String, String> header, String url, byte[] bytes, long size) {
        RequestBody body = RequestBody.create(okhttp3.MediaType.parse(""), bytes, 0, (int) size);
        Request request = new Request.Builder().put(body).url(url).build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String res = response.body().string();
                log.error("请求失败code:{}, body:{}", response.code(), res);
                throw new RuntimeException("请求失败：" + url);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public InputStream download2(Map<String, String> header, String url, Consumer<Map> consumer) {
        //header.put("Authorization", "Bearer " + token);
        Request.Builder builder = new Request.Builder();
        for (Map.Entry<String, String> entry : header.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            builder.header(key, value);
        }
        Request request = builder.url(url).build();
        try (Response downResponse = okHttpClient.newCall(request).execute()) {
            Headers headers = downResponse.headers();
            Map<String, String> resHeader = new HashMap<>();
            Iterator<Pair<String, String>> iterator = headers.iterator();
            while (iterator.hasNext()) {
                Pair<String, String> next = iterator.next();
                resHeader.put(next.getFirst(), next.getSecond());
            }
            consumer.accept(resHeader);
            log.debug("请求header:{}", header);
            log.debug("响应header:{} code:{}", resHeader, downResponse.code());
            if (!downResponse.isSuccessful()) {
                String res = downResponse.body().string();
                log.error("请求失败code:{}, body:{}", downResponse.code(), res);
                throw new RuntimeException("请求失败：" + url);
            }
            return downResponse.body().byteStream();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public InputStream download(Map<String, String> headers, String url, Consumer<Map> consumer) {
        URL httpUrl = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) httpUrl.openConnection();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            connection.setRequestProperty(entry.getKey(), entry.getValue());
        }
        connection.connect();
        InputStream inputStream = connection.getInputStream();
        Map<String, String> header = new HashMap<>();
        for (int i = 1; true; i++) {
            String key = connection.getHeaderFieldKey(i);
            if (key == null || "".equals(key)) break;
            String value = connection.getHeaderField(i);
            header.put(key, value);
        }
        log.debug("请求header:{}", headers);
        log.debug("响应header:{}", header);
        consumer.accept(header);
        return inputStream;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        File file = new File("token.json");
        if (!file.exists()) {
            file.createNewFile();
            this.token = this.readRefreshToken(file, refreshToken);
        } else {
            FileInputStream inputStream = new FileInputStream(file);
            String res = IOUtils.toString(inputStream);
            JSONObject obj = JSON.parseObject(res);
            Long expire = obj.getLong("expire");
            String refreshToken = obj.getString("refresh_token");
            if (expire < System.currentTimeMillis()) {
                this.token = this.readRefreshToken(file, refreshToken);
            } else {
                this.token = obj.getString("access_token");
            }
        }
    }

    private String readRefreshToken(File file, String refreshToken) throws Exception {
        Map body = new HashMap();
        body.put("grant_type", "refresh_token");
        body.put("refresh_token", refreshToken);
        String res = this.post(null, authUrl, body);
        JSONObject obj = JSON.parseObject(res);
        Long expire = obj.getLong("expires_in");
        obj.put("expire", System.currentTimeMillis() + expire * 1000);
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(obj.toString().getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        return obj.getString("access_token");
    }

}
